#ifndef __MODULE_H__
#define __MODULE_H__

#include <stdint.h>
#include <stdlib.h>
#include <memory.h>
#include <assert.h>
#include "ast.h"
#include "type.h"
#include "base.h"
#include "stmt.h"
#include "any.h"

#ifdef __cplusplus
#include <vector>
#include <string>
#include <map>
#endif

#ifndef WIN32
#define _strdup strdup
#endif

typedef void* hmodule_t;

int import_file(ast_t* ast, const char* filename);

int import_buffer(ast_t* ast, struct buffer_t* buf);

bool calc_error_occured();
void set_calc_error();
void clear_calc_error();

any_t calc_expr(void* p_env, ast_node_t* node, bool left = false);

any_t calc_statement(ast_node_t* node);

any_t calc_node(ast_node_t* node);

class_def_t* get_class(const char* name);

class auto_free
{
    void* ptr;
public:
    template <typename T>
    explicit auto_free(T* s): ptr(s) {}
    ~auto_free() { if (ptr) free(ptr); }
};

struct symbol_t
{
    char* name = nullptr;
    bool is_global = false;
};

class module_t
{
public:
public:
    module_t* parent = nullptr;
    module_t* kids = nullptr;
    std::vector<std::string> search_path;
    // load time
    std::vector<stmt_t*> statements;
    std::map<std::string, symbol_t> symbols;
    // runtime
    std::vector<any_t> values;
};

struct symbol_ext_t: public symbol_t
{
    int index = 0; // index in stack
    ~symbol_ext_t() {
        if (name)
            free(name);
        name = nullptr;
    }
    explicit symbol_ext_t(){}
    explicit symbol_ext_t(const symbol_ext_t& other)
    {
        memcpy(this, &other, sizeof(*this));
        name = _strdup(other.name);
    }
    symbol_ext_t& operator = (symbol_ext_t& other)
    {
        memcpy(this, &other, sizeof(*this));
        other.name = nullptr;
        return *this;
    }
};

void print_stack(env_t* env);

class env_t
{
private:
    std::vector<any_t> locals;
    env_t* parent = nullptr;
    typedef std::map<std::string, symbol_ext_t> symbol_map_t;
    symbol_map_t symbols;
public:
    const char* name = nullptr;
    location_t loc;
    bool return_done = false;
    bool break_done = false;
    bool continue_done = false;

    void clear()
    {
        clear_local();
        symbols.clear();
    }

    int layer() const {
        auto iter = this;
        int count = 0;
        while (iter) {
            count++;
            iter = iter->parent;
        }
        return count;
    }

    env_t* get_toplevel() const
    {
        auto env = this;
        while (env->parent) {
            env = env->parent;
        }
        return (env_t*)env;
    }

    env_t* get_parent() const
    {
        return parent;
    }
    void set_parent(env_t* env)
    {
        parent = env;
    }

    // check global here
    symbol_ext_t* find_symbol(const char* name) const
    {
        auto iter = symbols.find(name);
        if (iter == symbols.end())
            return nullptr;
        if (!iter->second.is_global || parent == nullptr) {
            return (symbol_ext_t*) & iter->second;
        }
        auto top = get_toplevel();
        assert(top);
        auto iter2 = top->find_symbol(name);
        if (iter2 == nullptr) {
            return nullptr;
        } else {
            return iter2;
        }
    }

    int local_count() const
    {
        return (int)locals.size();
    }

    const any_t& local_back() const
    {
        assert(locals.size() > 0);
        return locals.back();
    }

    const any_t& get_local(int i)
    {
        assert(i >= 0 && i < (int)locals.size());
        return locals[i];
    }

    void put_local(int i, const any_t& a)
    {
        assert(i >= 0 && i < (int)locals.size());
        locals[i] = a;
    }

    const any_t& get_local(const symbol_ext_t& sym)
    {
        return get_local(sym.index);
    }

    any_t get_expr(const char* name)
    {
        auto p = find_symbol(name);
        if (p == nullptr)
            return any_t();
        return get_local(p->index);
    }

    void set_symbol(const char* name, int i)
    {
        auto p = find_symbol(name);
        if (p)
            p->index = i;
        else {
            symbol_ext_t sym;
            sym.name = _strdup(name);
            sym.index = i;
            symbols[name] = sym;
        }
    }

    void add_global(const char* name)
    {
        symbol_ext_t sym;
        sym.name = _strdup(name);
        sym.index = -1;
        sym.is_global = true;
        symbols[name] = sym;
    }

    any_t get_value(const char* name) const
    {
        auto p = find_symbol(name);
        if (p == nullptr) {
            return any_t();
        }
        if (p->is_global && this->parent != nullptr) {
            return get_toplevel()->get_value(name);
        } else {
            return locals[p->index];
        }
    }

    any_t* get_value_ref(const char* name)
    {
        auto p = find_symbol(name);
        if (p == nullptr) {
            return nullptr;
        }
        if (p->is_global && this->parent != nullptr) {
            return get_toplevel()->get_value_ref(name);
        } else {
            if (!(p->index >= 0 && p->index < (int)locals.size())) {
                mark_line();
                set_calc_error();
                do_error(nullptr, "find the value ref: %s, but not in the stack\n", name);
                return nullptr;
            }
            return &locals[p->index];
        }
    }

    void put_value(const char* name, const any_t& a)
    {
        auto p = find_symbol(name);
        if (p == nullptr) {
            symbol_ext_t sym;
            sym.name = _strdup(name);
            sym.index = (int)locals.size();
            if (parent == nullptr) {
                sym.is_global = true;
            }
            locals.push_back(a);
            symbols[name] = sym;
            return;
        }
        if (p->is_global && this->parent != nullptr) {
            get_toplevel()->put_value(name, a);
        } else {
            locals[p->index] = a;
        }
    }

    // for function call
    const any_t& get_argument(int i)
    {
        assert(i >= 0 && i < (int)locals.size());
        return locals[locals.size() - 1 - i];
    }

    // for function call
    void push_local(const any_t& a)
    {
        locals.push_back(a);
    }

    // for function call
    void clear_local()
    {
        locals.clear();
    }

    void put_expr(expr_t* expr, const any_t& a, bool with_warning = false)
    {
        auto ref = get_expr_ref(expr);
        if (ref != nullptr) {
            *ref = a;
        }
        else {
            assert(0 && "can't put to value to a nullptr");
        }
    }

    any_t* do_get_expr_ref(expr_t* expr, bool with_warning = false, bool make = true);
    any_t* get_expr_ref(expr_t* expr, bool with_warning = false, bool make = true);

    any_t get_expr(expr_t* expr)
    {
        any_t ret;
        assert(expr->op == OP_ID);
        auto iter = find_symbol(ref_to_string(&expr->token.ss));
        if (iter == nullptr) {
            auto ref = get_expr_ref(expr, false, false);
            if (ref) {
                return *ref;
            }
            // a unknown var considered as declared as 0
            // mark_line();
            do_error(&expr->token.loc, "can't get var value: %s", ref_to_string(&expr->token.ss));
            set_calc_error();
            if (get_class(ref_to_string(&expr->token.ss))) {
                printf("'%s' is a class name, do you want to make a object of the class?\n",
                    ref_to_string(&expr->token.ss));
            }
            print_stack(this);
            return any_t();
        }
        return get_value(ref_to_string(&expr->token.ss));
    }

    void print_symbols() const
    {
        for (auto iter : symbols)
        {
            const auto& name = iter.first;
            const auto& sym = iter.second;
            printf("%s: ", name.c_str());
            print_any(get_value(name.c_str()));
            if (sym.is_global) {
                printf(" (global)");
            }
            printf("\n");
        }
    }
};

// just for memory leak check
void free_globals();

extern env_t* g_top_env;

extern class_def_t* g_class_array;
extern class_def_t* g_class_dict;
extern class_def_t* g_class_string;

#endif  // __MODULE_H__
